/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
 */

import { IncrementalTask } from '../external/task/incremental-task.js';
import { FileSet } from '../internal/snapshot/util/file-set.js';
import { TaskInputValue } from '../internal/snapshot/util/task-input-value-entry.js';
import { formatTime } from '../util/time-util.js';

import { DecoratorDeclarations, Strategies } from './strategies.js';
import { getSelfDeclaratorContainer, hasIncrementalDecorator, incrementalDeclaratorLogger as _log } from './util.js';

const isIncrementalTask = (task: any) => {
  return !!task && (task instanceof IncrementalTask);
};

/**
 * 从当前task对象开始，基于原型链向上查找使用增量输入/输出声明的装饰器装饰的成员变量
 * 找到IncrementalTask后停止
 * @param task
 */
const collectDecoratorDecorations = (task: IncrementalTask) => {
  const priorityKeys = new Set<string>();
  const decoratorDeclarations: DecoratorDeclarations = {
    inputs: new Map(),
    inputFiles: new FileSet(),
    outputFiles: new FileSet(),
    ignoreDecoratorTypes: new Set(),
  };

  let target = task;

  while (isIncrementalTask(target)) {
    const declarations = getSelfDeclaratorContainer(target) || [];
    declarations
      .filter(({ declaratorType, propertyKey }) => {
        const ignored = decoratorDeclarations.ignoreDecoratorTypes.has(declaratorType);
        if (ignored) {
          _log.debug(`Incremental declaration: '${propertyKey}' will be ignored.`)
        }
        //已经被ignore的，不需要处理
        return !ignored;
      })
      .forEach((declaration) => {
        const { propertyKey, declaratorType, options } = declaration;

        // 对于同名key，越接近当前对象，优先级越高
        if (priorityKeys.has(propertyKey)) {
          return;
        }
        priorityKeys.add(propertyKey);
        const propertyValue = task[propertyKey as keyof typeof task];
        try {
          Strategies[declaratorType](propertyKey, propertyValue, decoratorDeclarations, options);
        } catch {
          // 报warning
          _log.warn(`Error occurred while setting '${declaratorType}' by '${propertyKey}' in task '${task.getPath()}', will treat it as normal property.`);
        }
      });

    // 基于原型链向上查找
    target = Object.getPrototypeOf(target);
  }

  return decoratorDeclarations;
};

/**
 * 重设当前任务对象的declareInputs函数，后续基于重设后的函数做增量相关逻辑处理：
 * 1.调用原有的declareInputs，获取到返回的Map
 * 2.把装饰器装饰的属性的key和value，放入Map
 * @param task
 * @param inputs
 */
const wrapDeclareInputs = (task: IncrementalTask, inputs: Map<string, TaskInputValue>) => {
  if (!inputs.size) {
    return;
  }
  const old = task.declareInputs.bind(task);
  task.declareInputs = function () {
    const map = old();
    for (const [key, value] of inputs) {
      map.set(key, value);
    }
    return map;
  };
};

const concatFileSet = (dest: FileSet, source: FileSet): FileSet => {
  for (const [key, value] of source.collect()) {
    dest.addEntry(key, value ? { ...value } : undefined);
  }
  return dest;
};

/**
 * 重设当前任务对象的declareInputFiles函数，后续基于重设后的函数做增量相关逻辑处理：
 * 1.调用原有的declareInputFiles，获取到返回的FileSet
 * 2.把装饰器装饰的属性的value，放入FileSet
 * @param task
 * @param inputFiles
 */
const wrapDeclareInputFiles = (task: IncrementalTask, inputFiles: FileSet) => {
  const group = inputFiles.collect();
  if (!group.size) {
    return;
  }
  const old = task.declareInputFiles.bind(task);
  task.declareInputFiles = function () {
    return concatFileSet(old(), inputFiles);
  };
};

/**
 * 重设当前任务对象的declareOutputFiles函数，后续基于重设后的函数做增量相关逻辑处理：
 * 1.调用原有的declareOutputFiles，获取到返回的FileSet
 * 2.把装饰器装饰的属性的value，放入FileSet
 * @param task
 * @param outputFiles
 */
const wrapDeclareOutputFiles = (task: IncrementalTask, outputFiles: FileSet) => {
  const group = outputFiles.collect();
  if (!group.size) {
    return;
  }
  const old = task.declareOutputFiles.bind(task);
  task.declareOutputFiles = function () {
    return concatFileSet(old(), outputFiles);
  };
};

/**
 * 新增使用装饰器来声明任务的增量输入/输出机制。
 * 该机制是基于原有的增量机制上进行扩展，所以可以保证两套机制的并存。
 * 该函数主要做了两部分工作：
 * 1.从当前任务开始，基于原型链向上寻找，找出所有使用装饰器来声明的增量输入/输出
 * 2.如果存在对应的增量输入/输出，为了保证这部分输入/输出能够被后续获取，重设该任务的declareInputs, declareInputFiles，declareOutputFiles，
 * @param task
 */
export const wrapIncrementalDeclarations = (task: IncrementalTask) => {
  if (!hasIncrementalDecorator(task)) {
    return;
  }

  const beginTime = process.hrtime();
  const { inputs, inputFiles, outputFiles } = collectDecoratorDecorations(task);
  wrapDeclareInputs(task, inputs);
  wrapDeclareInputFiles(task, inputFiles);
  wrapDeclareOutputFiles(task, outputFiles);
  const endTime: [number, number] = process.hrtime(beginTime);
  const realTime = formatTime(endTime);
  _log.debug(`Task '${task.getPath()}' cost while wrapping incremental declarations: ${realTime}`);
};
